﻿using Common.Json;
using Common.Logger;
using Common.Web.Attributes;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;

namespace Common.Web
{
    /// <summary>
    /// 对失败的请求进行log记录
    /// </summary>
    public class FailureLogFilterAttribute : ActionFilterAttribute
    {
        private static string exceptHeaders;
        private readonly IConfiguration Configuration;
        /// <summary>
        /// 对失败的请求进行log记录
        /// </summary>
        /// <param name="configuration"></param>
        public FailureLogFilterAttribute(IConfiguration configuration)
        {
            this.Configuration = configuration;
            exceptHeaders = this.Configuration["headersNoLog"];
        }
        /// <summary>
        /// Result对象执行之前
        /// </summary>
        /// <param name="context"></param>
        public override void OnResultExecuting(ResultExecutingContext context)
        {
            base.OnResultExecuting(context);
            bool ignore = context.ActionDescriptor.EndpointMetadata.OfType<NoFailureLogAttribute>().Any()
                || context.Controller.GetType().GetCustomAttribute<NoFailureLogAttribute>() != null;
            if (!ignore && context.Result is ObjectResult objRes && objRes.Value is FuncResult retRes && !retRes)
            {
                LogFailure(retRes, context.HttpContext);
            }
        }

        internal static void LogFailure(FuncResult retRes, HttpContext httpContext)
        {
            try
            {
                string json = JsonSerializer.JsonSerialize(retRes);
                string body = GetRequestBody(httpContext);
                StringBuilder logText = new StringBuilder();
                logText.AppendLine($"请求接口：{httpContext.Request.Method} {httpContext.Request.Path}{httpContext.Request.QueryString}");
                logText.AppendLine($"返回值：{json}");
                var headers = FilterHeaders(httpContext.Request.Headers);
                if (headers.Count > 0)
                {
                    logText.AppendLine($"请求头：{JsonSerializer.JsonSerialize(headers)}");
                }
                if (!string.IsNullOrEmpty(body))
                {
                    logText.AppendLine($"请求体：{body}");
                }

                Log<FailureLogFilterAttribute>.Error(logText.ToString());
            }
            catch (Exception ex)
            {
                Log<FailureLogFilterAttribute>.Error("失败的请求Logger出现异常", ex);
            }
        }
        internal static string GetRequestBody(HttpContext ctx)
        {
            if ("GET".Equals(ctx.Request.Method))
            {
                return null;
            }
            StreamReader reader = new StreamReader(ctx.Request.Body);
            ctx.Request.Body.Seek(0, SeekOrigin.Begin);
            return reader.ReadToEndAsync().ConfigureAwait(false).GetAwaiter().GetResult();
        }
        private static IDictionary<string, string> FilterHeaders(IHeaderDictionary headers)
        {
            List<string> except = new List<string>
            {
                "Host","Content-Type","Accept","Connection","Accept-Encoding","Accept-Language","Cookie","Referer","Upgrade-Insecure-Requests",
                "Origin","Content-Length","X-Real-IP","X-Original-URL"
            };
            //string exceptHeaders = this.Configuration["headersNoLog"];
            string[] extra = exceptHeaders?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
            if (extra != null && extra.Length > 0)
            {
                foreach (var item in extra)
                {
                    if (!except.Contains(item))
                    {
                        except.Add(item);
                    }
                }
            }
            var dict = new Dictionary<string, string>();
            foreach (var kv in headers)
            {
                if (!except.Contains(kv.Key, StringComparer.OrdinalIgnoreCase))
                {
                    dict.Add(kv.Key, kv.Value);
                }
            }
            return dict;
        }
    }

}
